﻿namespace TanzimSaqib.NoBrainer
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Xml.Linq;
    using System.Web.UI;
    using System.IO;

    /// <summary>
    /// WebForms should inherit this class.
    /// </summary>
    /// <typeparam name="C">Controller that serves this page.</typeparam>
    /// <typeparam name="M">Model that is supposed to be bound to this page.</typeparam>
    public class ViewBase<C, M> : Page where M : ModelBase
    {
        #region Properties

        public C Controller { get; set; }
        public M Model { get; set; }
        private Lookup<string, ViewControl> ControlGraph { get; set; }
        private List<ViewControl> ControlList { get; set; }

        public Dictionary<string, ViewCmsControl> Cms = new Dictionary<string, ViewCmsControl>();

        private string _FullName;
        private string FullName
        {
            get
            {
                if (string.IsNullOrEmpty(_FullName))
                {
                    _FullName = GetType().FullName;    
                }

                return _FullName;
            }
        }

        private XElement _CmsMvcXml;
        public XElement CmsMvcXml
        {
            get
            {
                if (_CmsMvcXml == null || _CmsMvcXml.IsEmpty)
                {
                    _CmsMvcXml = ((XElement) CacheManager.Instance.Get(CacheManager.CmsMvcXml(CmsMvcXmlFilePath)));
                }

                return _CmsMvcXml;
            }

            set
            {
                _CmsMvcXml = value;
                CacheManager.Instance.Set(CacheManager.CmsMvcXml(CmsMvcXmlFilePath), _CmsMvcXml);
            }
        }

        private string _CmsMvcXmlFilePath;
        public string CmsMvcXmlFilePath
        {
            get
            {
                if(string.IsNullOrEmpty(_CmsMvcXmlFilePath))
                {
                    _CmsMvcXmlFilePath = string.Format("{0}\\{1}.xml", Engine.CmsXmlDirectory, Controller.GetType().FullName);
                }

                return _CmsMvcXmlFilePath;
            }
        }

        private PropertyInfo[] UserProperties { get; set; }

        #endregion

        #region Func

        private static readonly Func<ModelBase, PropertyInfo[]> GetUserProperties =
            Model => Model.GetType().GetProperties()
                        .Where(property => !ViewUtilities.IsPropertyForInternalUse(property.Name)).ToArray();

        #endregion 

        #region Public

        public ViewBase()
        {
            Controller = (C)Activator.CreateInstance(typeof(C));
            Model = (M) Activator.CreateInstance(typeof (M));
            
            Model.ErrorMessages = new Dictionary<string, string>();
            Model.CmsErrorMessages = new Dictionary<string, bool>();
        }

        public void InvokeController(Func<M, M> func)
        {
            // Phase 3: Bind content (CMS)
            // Phase 4: Bind updated Model to the UI
            // Phase 5: Bind errors to the UI
            BindModel(() =>
                              {
                                  EnsureControlGraph();

                                  // Phase 1: Bind the Model from UI
                                  Model = ViewUtilities.Bind(this, Model, ControlGraph, UserProperties);

                                  // Phase 2: Invoke Controller method
                                  Model = func(Model);
                              });
        }

        /// <summary>
        /// Call when Model is modified in web layer, eg. retrieved from Session 
        /// </summary>
        public void BindModel(Action preBindModel)
        {
            preBindModel();

            //EnsureControlGraph(); //This is not required as this is called already

            // Phase 3: Bind content (CMS)
            BindContent();

            // Phase 4: Bind updated Model to the UI
            ViewUtilities.BindView(this, Model, ControlGraph, UserProperties);

            // Phase 5: Bind errors to the UI
            ViewUtilities.BindErrors(this, Model, ControlGraph);
        }

        public void BindContent()
        {
            EnsureControlGraph();

            ViewUtilities.BindCmsInputControls(this, Cms);

            DataBind();
        }

        #endregion

        #region Protected

        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            EnsureControlGraph();
        }

        #endregion

        #region Private

        private void EnsureControlGraph()
        {
            if (UserProperties == null)
                UserProperties = GetUserProperties(Model);

            if ((ControlGraph != null && ControlList != null) && ControlList.Count >= 1)
            {
                 return;
            }

            ControlList = ViewUtilities.BuildControlGraph(this, Model);
            ControlGraph = (Lookup<string, ViewControl>)(from c in ControlList select c).ToLookup(c => c.ModelProperty, c => c);

            if (!File.Exists(CmsMvcXmlFilePath))
                return;

            using (var reader = new StreamReader(CmsMvcXmlFilePath))
            {
                if (CmsMvcXml == null)
                {
                    CmsMvcXml = XElement.Parse(reader.ReadToEnd());
                }

                Cms = ViewUtilities.GetViewCmsControls(CmsMvcXml, FullName);
            }
        }

        #endregion
    }
}
